package dd.lo.chapter8;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.videoio.VideoCapture;
import org.opencv.videoio.Videoio;

import java.io.ByteArrayInputStream;

/**
 * Chapter8，练习1，Create a program that (1) reads frames from a video, (2) turns the result to grayscale, and (3) performs Canny edge detection on the image.
 */
public class Exercise1 extends Application {
    private VideoCapture cap;

    private int vw;
    private int vh;

    private ImageView imageView;

    private volatile boolean running = true;
    public static void main(String[] args) {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
        launch(args);
    }

    @Override
    public void start(Stage stage) {
        //读取一个视频
        cap = new VideoCapture("/Users/kwok/Downloads/young_women_listening_to_music.mp4");
        if (!cap.isOpened()) {
            throw new RuntimeException("视频读取失败");
        }
        int fw = (int) cap.get(Videoio.CAP_PROP_FRAME_WIDTH);
        int fh = (int) cap.get(Videoio.CAP_PROP_FRAME_HEIGHT);
        System.out.printf("Video resolution: (%d, %d)\n", fw, fh);
        //由于是3个图片同时展示，比较大，所以缩小一下图像大小，而且小图像时运算会比较快
        vw = fw / 4;
        vh = fh / 4;
        imageView = new ImageView();
        imageView.setFitWidth(3 * vw);
        imageView.setFitHeight(vh);
        BorderPane root = new BorderPane();
        root.setCenter(imageView);
        Scene scene = new Scene(root, 3 * vw, vh);
        scene.setOnKeyReleased(keyEvent -> {
            //按下esc可以关闭程序
            if (KeyCode.ESCAPE == keyEvent.getCode()) {
                running = false;
                stage.close();
            }
        });
        stage.setScene(scene);
        stage.setTitle("Exercise1-Display origin, gray, canny result");
        stage.setResizable(false);
        startVideo();
        stage.show();
    }

    private void startVideo() {
        Task<Void> task = new Task<Void>() {
            //做一个比较宽的图可以把原图、灰度、边缘图全部塞在一起的
            private Mat img = new Mat(vh, 3 * vw, CvType.CV_8UC3);
            //原图
            private Mat origin = new Mat();
            //灰度图
            private Mat gray = new Mat();
            //边缘检测图
            private Mat canny = new Mat();
            @Override
            protected Void call() {
                while (true) {
                    if (!running) {
                        System.out.println("Exiting...");
                        cap.release();
                        break;
                    }
                    cap.read(origin);
                    if (origin.empty()) {
                        System.out.println("No image read! Exiting...");
                        break;
                    }
                    //先缩小一下读出的帧原图
                    Imgproc.resize(origin, origin, new Size(vw, vh));
                    //转灰度
                    Imgproc.cvtColor(origin, gray, Imgproc.COLOR_BGR2GRAY);
                    //利用灰度图边缘检测
                    Imgproc.Canny(gray, canny, 10, 100, 3, true);
                    //这里有个大坑，由于我的img是CV_8UC3，而BGR2GRAY得到的Mat只有1个通道，后续copyTo的时候会导致不能copy过去
                    //可能是因为通道不同所以不能复制过去，以后要小心通道数量的问题，这里将灰度图和边缘检测图重新转回3通道，就可以copyTo了
                    Imgproc.cvtColor(gray, gray, Imgproc.COLOR_GRAY2BGR);
                    Imgproc.cvtColor(canny, canny, Imgproc.COLOR_GRAY2BGR);
                    //下面利用ROI的方法将几个小图复制到大图的对应区域
                    //Origin image
                    Mat originRoi = new Mat(img, new Rect(0, 0, vw, vh));
                    origin.copyTo(originRoi);
                    //Gray image
                    Mat grayRoi = new Mat(img, new Rect(vw, 0, vw, vh));
                    gray.copyTo(grayRoi);
                    //Canny image
                    Mat cannyRoi = new Mat(img, new Rect(2 * vw, 0, vw, vh));
                    canny.copyTo(cannyRoi);
                    //显示大图
                    MatOfByte buffer = new MatOfByte();
                    Imgcodecs.imencode(".jpeg", img, buffer);
                    Platform.runLater(() -> imageView.setImage(new Image(new ByteArrayInputStream(buffer.toArray()))));
                }
                return null;
            }
        };
        new Thread(task).start();
    }
}
